package model.impl.network;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import main.ResOTCModel;

import org.resotc.dao.CentroidDAO;
import org.resotc.dao.IntersectionDAO;
import org.resotc.model.Centroid;
import org.resotc.model.Disturbance;
import org.resotc.model.Element;
import org.resotc.model.ElementType;
import org.resotc.model.Individual;
import org.resotc.model.Intersection;
import org.resotc.model.Lane;
import org.resotc.model.Queue;
import org.resotc.model.Street;
import org.resotc.model.Track;

import sim.app.woims.Vector2D;
import sim.engine.SimState;
import sim.engine.Steppable;
import sim.portrayal.simple.ShapePortrayal2D;
import sim.util.Double2D;

public class LaneImpl extends ElementImpl implements Lane, Steppable {

	public static final boolean RIGHT_HAND_SIDE_OF_ROAD = true;
	public static final boolean LEFT_HAND_SIDE_OF_ROAD = false;
	final int dir;

	private int id;
	private int fromId;
	private ElementType fromType;
	private int toId;
	private ElementType toType;
	private boolean sideOfRoad;
	private Element predecessor;
	private Element successor;
	private List<Individual> currentIndividuals;
	private double capacity;
	private boolean isCrowded;
	private HashMap<Integer, Queue> queues = new HashMap<Integer, Queue>();
	private HashMap<Integer, Street> streets = new HashMap<Integer, Street>();
	private Street street;
	private IntersectionDAO intersectionDAO;
	private CentroidDAO centroidDAO;
	private HashMap<Integer, Track> dstTracks = new HashMap<Integer, Track>();
	private HashMap<Integer, Track> srcTracks = new HashMap<Integer, Track>();
	private Double2D center;
	private double[] shapeXCoordinates;
	private double[] shapeYCoordinates;
	private ShapePortrayal2D shape;
	private HashMap<Individual, Double> IndAtLane;
	private HashMap<Integer, Disturbance> disturbances;
	private Double2D prePos;
	private Double2D sucPos;

	public LaneImpl(int id, int fromId, ElementType fromType, int toId,
			ElementType toType, boolean sideOfRoad,
			HashMap<Integer, Track> dstTracks,
			HashMap<Integer, Track> srcTracks, CentroidDAO centroidDAO,
			IntersectionDAO intersectionDAO,
			HashMap<Integer, Disturbance> disturbances) {
		super(id, ElementType.LANE);
		this.id = id;
		this.fromId = fromId;
		this.fromType = fromType;
		this.toId = toId;
		this.toType = toType;
		this.sideOfRoad = sideOfRoad;
		this.intersectionDAO = intersectionDAO;
		this.centroidDAO = centroidDAO;
		dir = sideOfRoad == RIGHT_HAND_SIDE_OF_ROAD ? 1 : -1;
		currentIndividuals = Collections
				.synchronizedList(new LinkedList<Individual>());
		IndAtLane = new HashMap<Individual, Double>();
		this.disturbances = disturbances;
		for (Integer trackid : dstTracks.keySet()) {
			this.dstTracks.put(trackid, dstTracks.get(trackid));
		}
		for (Integer trackid : srcTracks.keySet()) {
			this.srcTracks.put(trackid, srcTracks.get(trackid));
		}
	}

	/**
	 * @see Lane#getPredecessor()
	 */
	public Element getPredecessor() {
		if (fromType == ElementType.CENTROID) {
			return this.predecessor = centroidDAO.getCentroid(fromId);
		} else {
			return this.predecessor = intersectionDAO.getIntersection(fromId);
		}
	}

	/**
	 * @see Lane#getSuccessor()
	 */
	public Element getSuccessor() {
		if (toType == ElementType.CENTROID) {
			return this.successor = centroidDAO.getCentroid(toId);
		} else {
			return this.successor = intersectionDAO.getIntersection(toId);
		}
	}

	/**
	 * @see Lane#setPredecessorCoordinate(double, double)
	 */
	public void setPredecessorCoordinate(double x, double y) {
		getPredecessor().setX(x);
	}

	/**
	 * @see Lane#setSuccessorCoordinate(double, double)
	 */
	public void setSuccessorCoordinate(double x, double y) {
		getSuccessor().setY(y);
	}

	/**
	 * @see Lane#getCurrentIndividuals()
	 */
	public List<Individual> getCurrentIndividuals() {
		return currentIndividuals;
	}

	/**
	 * @see Lane#getToType()
	 */
	public ElementType getToType() {
		return this.toType;
	}

	/**
	 * @see Lane#getFromType()
	 */
	public ElementType getFromType() {
		return this.fromType;
	}

	/**
	 * @see Lane#getFromId()
	 */
	public int getFromId() {
		return this.fromId;
	}

	/**
	 * @see Lane#getToId()
	 */
	public int getToId() {
		return this.toId;
	}

	/**
	 * @see Lane#addIndividualToLane(Individual)
	 */
	public void addIndividualToLane(Individual ind) {
		this.currentIndividuals.add(ind);
		ind.setLaneId(this.id);
	}

	/**
	 * @see Lane#deleteIndividualFromLane(Individual)
	 */
	public void deleteIndividualFromLane(Individual ind) {
		if (currentIndividuals.contains(ind)) {
			currentIndividuals.remove(ind);
		}
	}

	/**
	 * @see Lane#isIndividualBetweenPosition(double, double)
	 */
	public boolean isIndividualBetweenPosition(double oldPos, double newPos) {
		Boolean isIndividualBetweenPosition = false;
		List<Double> positions = new ArrayList<Double>(IndAtLane.values());
		for (Double position : positions) {
			if (position > oldPos && position <= newPos + 1) {
				isIndividualBetweenPosition = true;
				break;
			}

		}
		return isIndividualBetweenPosition;
	}

	/**
	 * @see Lane#calculateSpeed(double)
	 */
	public double calculateSpeed(double pos) {
		double speed = 100;
		List<Disturbance> disturbanceList = new ArrayList<Disturbance>(
				this.disturbances.values());

		for (Disturbance disturbance : disturbanceList) {
			if (disturbance.getLaneId() == this.id) {
				double fromDist = disturbance.getFromArea() / 100;
				double toDist = disturbance.getToArea() / 100;
				if (this.getLength() * fromDist <= pos
						&& this.getLength() * toDist >= pos) {
					speed = 100 - disturbance.getStrenge();
				}
			}
		}
		return speed;
	}

	/**
	 * @see Lane#step(SimState)
	 */
	public synchronized void step(SimState state) {

		ResOTCModel simulation = (ResOTCModel) state;
		this.queues = simulation.getQueueDAO().getQueues();
		this.streets = simulation.getStreetDAO().getStreets();
		setCurrentStreet(this.streets);
		if (this.currentIndividuals.size() != 0) {

			// iterate over individuals and move them accoarding to position and
			// speed

			// iterate over individuals and move them accoarding to position and
			// speed
			Iterator<Individual> individualsIterator = currentIndividuals
					.iterator();
			while (individualsIterator.hasNext()) {
				Individual individual = individualsIterator.next();
				// lookIsCrowded();

				simulation.cars.setObjectLocation(individual,
						individual.getPosition());
				// calculate new position
				double calculateSpeed = this.calculateSpeed(individual
						.getRelativeLinkPosition());
				double newPos = individual.getRelativeLinkPosition()
						+ (individual.getDesiredSpeed() * (calculateSpeed / 100));
				while (this.isIndividualBetweenPosition(
						individual.getRelativeLinkPosition(), newPos)
						&& (newPos - individual.getRelativeLinkPosition() > 0.5)) {

					double relativePos = individual.getRelativeLinkPosition();
					double individualDesireSpeed = individual.getDesiredSpeed();
					calculateSpeed = calculateSpeed * 0.7 / 100;
					newPos = individual.getRelativeLinkPosition()
							+ (individual.getDesiredSpeed() * calculateSpeed);

				}
				
				individual.setRelativeLinkPosition(newPos,
						this.getPredecessor2D(), this.getSuccessor2D());
				simulation.cars.setObjectLocation(individual,
						individual.getPosition());
				IndAtLane.remove(individual);
				IndAtLane.put(individual, newPos);

				if (newPos > this.getLength()) {

					Intersection intersection;
					Centroid centroid;
					// individual has left the lane
					// calculate position after successor in the new lane
					// individual.setRelativeLinkPosition(newPos +
					// this.getLength(),this.getPredecessor(),this.getSuccessor());
					// remove individual from currentIndividuals on the lane
					if (this.getSuccessor().getElementType() == ElementType.INTERSECTION) {
						intersection = intersectionDAO.getIntersection(this
								.getSuccessor().getId());
						List<Track> arrivingTracks = new ArrayList<Track>();
						for (Track track : dstTracks.values()) {
							arrivingTracks.add(track);
						}
						Random random = new Random();
						Track arrivingTrack = arrivingTracks.get(random
								.nextInt(arrivingTracks.size()));

						if (!arrivingTrack.isTrackOverloaded()) {
							intersection.addArrivingIndividual(individual,
									arrivingTrack.getId());
							individual.resetRelativeLinkPosition();
							individualsIterator.remove();
							IndAtLane.remove(individual);

							simulation.cars.remove(individual);
						
						}
						
					} else if (this.getSuccessor().getElementType() == ElementType.CENTROID) {
						centroid = centroidDAO.getCentroid(this.getSuccessor()
								.getId());
						individual.setTravelTime(individual
								.calculateTravelTime(simulation.schedule
										.getTime()));
						centroid.addArrivingIndividual(individual);
						individual.resetRelativeLinkPosition();

						IndAtLane.remove(individual);
						simulation.cars.remove(individual);
						individualsIterator.remove();


					}

				}

			}
		}
	}

	/**
	 * @see Lane#getCapacity()
	 */
	public double getCapacity() {
		return capacity;
	}

	/**
	 * @see Lane#setCapacity()
	 */
	public void setCapacity() {
		double totalLength = 0;
		for (Individual ind : currentIndividuals) {
			totalLength += ind.getLength();
		}
		double laneUnit = this.getLength() / 100;
		capacity = totalLength / laneUnit;
	}

	/**
	 * @see Lane#setCurrentStreet()
	 */
	public void setCurrentStreet(HashMap<Integer, Street> streetDAO) {
		for (Street street : streetDAO.values()) {
			HashMap<Integer, Lane> lanes = street.getLanes();
			for (Lane lane : lanes.values()) {
				if (lane.getId() == this.getId()) {
					this.street = street;
				}
			}
		}
	}

	/**
	 * @see Lane#setCapacity()
	 */
	public Boolean tryChangeLane(Individual individual, double newPos) {
		Boolean success = false;
		HashMap<Integer, Lane> lanes = this.street.getLanes();
		for (Lane lane : lanes.values()) {
			if (lane.getSuccessor().getId() == this.getSuccessor().getId()
					&& lane.getPredecessor().getId() == this.getPredecessor()
							.getId()
					&& lane.getSuccessor().getElementType() == this
							.getSuccessor().getElementType()
					&& lane.getPredecessor().getElementType() == this
							.getPredecessor().getElementType()) {
				if (!lane.isIndividualBetweenPosition(
						individual.getRelativeLinkPosition(), newPos)) {
					success = true;
					lane.addIndividualToLane(individual);
				}
			}
		}
		return success;
	}

	/**
	 * @see Lane#getLength()
	 */
	public double getLength() {

		double x = getPredecessor().getX() - getSuccessor().getX();
		double y = getPredecessor().getY() - getSuccessor().getY();
		Vector2D lane = new Vector2D(x, y);
		return lane.length();
	}

	/**
	 * @see Lane#setIsCrowded(boolean)
	 */
	public void setIsCrowded(boolean isCrowded) {
		this.isCrowded = isCrowded;
	}

	/**
	 * @see Lane#setIsCrowded(boolean)
	 */
	public void lookIsCrowded() {
		Boolean isCrowded = false;
		List<Double> positions = new ArrayList<Double>(IndAtLane.values());
		for (Double position : positions) {
			if (position == 0) {
				isCrowded = true;

			}
		}
		this.setIsCrowded(isCrowded);
	}

	/**
	 * @see Lane#isCrowded()
	 */
	public boolean isCrowded() {
		return isCrowded;
	}

	/**
	 * @see Lane#getMotorcadeLength()
	 */
	public double getMotorcadeLength() {
		double length = 0;

		for (Individual ind : currentIndividuals) {
			length += ind.getLength();
		}
		return length;
	}

	/**
	 * @see Lane#initializeQueues(HashMap)
	 */
	public void initializeQueues(HashMap<Integer, Queue> queues) {
		List<Queue> queueList = new ArrayList<Queue>(queues.values());
		for (Queue queue : queueList) {

			this.queues.put(queue.getId(), queue);

		}
	}

	public int getDir() {
		return dir;
	}

	public int determinePredecessorQuadrant() {
		double xPre = getPredecessor().getX();
		double yPre = getPredecessor().getY();

		double xSuc = getSuccessor().getX();
		double ySuc = getSuccessor().getY();

		if (xSuc > xPre) {

		}

		return 0;
	}

	public void initializeLanes(HashMap<Integer, Lane> lanes) {
		// TODO Auto-generated method stub

	}

	public HashMap<Integer, Track> getDstTracks() {
		return dstTracks;
	}

	public HashMap<Integer, Track> getSrcTracks() {
		return srcTracks;
	}

	/**
	 * @see Lane#getCenter(Element, Element)
	 */
	public Double2D getCenter() {
		return center;
	}

	/**
	 * @see Lane#getShapeXCoordinates()
	 */
	public double[] getShapeXCoordinates() {
		return shapeXCoordinates;
	}

	/**
	 * @see Lane#getShapeYCoordinates()
	 */
	public double[] getShapeYCoordinates() {
		return shapeYCoordinates;
	}

	/**
	 * @see Lane#setShapeXCoordinates(double[])
	 */
	public void setShapeXCoordinates(Double2D center) {
		this.shapeXCoordinates = shapeXCoordinates;
	}

	/**
	 * @see Lane#setShapeYCoordinates(double[])
	 */
	public void setShapeYCoordinates() {

	}

	public void setShapeXCoordinates() {
		// TODO Auto-generated method stub{5, -5, -5, 5}

	}

	// public Double2D getCenter(Double2D pre, Double2D suc) {
	// double x = (pre.getX() + suc.getX()) / 2;
	// double y = (pre.getY() + suc.getY()) / 2;
	// return new Double2D(x, y);
	// }

	public void mountShape() {
		this.shape = new ShapePortrayal2D(shapeXCoordinates, shapeYCoordinates);
	}

	public ShapePortrayal2D getShape() {
		return this.shape;
	}

	public void setShapeXCoordinates(double[] shapeXCoordinates) {
		this.shapeXCoordinates = shapeXCoordinates;
	}

	public void setShapeYCoordinates(double[] shapeYCoordinates) {
		this.shapeYCoordinates = shapeYCoordinates;
	}

	public void setShapeXCoordinates(double a, double b, double c, double d) {

	}

	public void setShapeYCoordinates(double a, double b, double c, double d) {
		// TODO Auto-generated method stub

	}

	public void setCenter(Double2D center) {
		this.center = center;
	}

	public Double2D getPredecessor2D() {
		return prePos;
	}

	public Double2D getSuccessor2D() {
		return sucPos;
	}

	public void setPredecessor2D(Double2D prePos) {
		this.prePos = prePos;
	}

	public void setSuccessor2D(Double2D sucPos) {
		this.sucPos = sucPos;
	}

	public void setCenter(Double2D pre, Double2D suc) {
		this.center = new Double2D((pre.x + suc.x) / 2, (pre.y + suc.y) / 2);
	}

}
